#include <iostream>
#include <cstdio>
#include <unistd.h>
#include <string>
#include <signal.h>
#include <sys/types.h>

void handler(int signo) // signo:信号编号
{
    std::cout << "进程捕捉到了一个信号，信号的编码是：" << signo << std::endl;
    // exit(0);
}

//  ./myprocess pid signo
static void Usage(const std::string &proc) // 使用方法
{
    std::cout << "\nUsage: " << proc << " pid signo\n"
              << std::endl;
}

void catchSig(int signo)
{
    std::cout << "获取到一个信号，信号编号是：" << signo << std::endl;
}

int main(int argc, char *argv[])
{

    // 如果我把所有信号的做不退出处理会如何？  -- > 9号信号的动作无法更改！
    // for(int signo = 1;signo < 31; signo++)
    // {
    //     signal(signo,catchSig);
    // }

    // while(true) sleep(1);

    /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    // 4.软件条件 -- "闹钟"其实就是用软件实现的
    // alarm(1);//一秒后发,闹钟
    // IO其实很慢
    // 统计1S左右，我们的计算机能够将数据累计多少次！
    
    // int cnt = 0;
    // while(true)
    // {
    //     std::cout << "cnt:" << cnt++ << std::endl;
    // }



    /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    // 3.硬件异常产生信号
    // 信号产生，不一定非得用户显示的发生！
    // signal(11, catchSig); // 能向我写的函数传递SIGFPE参数
    // // int a = 10;
    // // a /= 0; // 除0错误
    // // 我只是除了一次0，但是OS一直给我发送8号信号

    // while (true)
    // {
    //     std::cout << "我在运行中..." << std::endl;
    //     sleep(1);
    //     int* p = nullptr;
    //     *p = 100; //为什么 野指针 就会崩溃呢？ 因为OS给进程发送指定的11号信号
    //     //OS怎么知道野指针了呢？映射 = 页表+MMU
    //     // MMU在CPU里面，OS也会识别MMU异常
    // }

    // 为什么除0 会终止进程？ 当前进程会收到来自OS系统的信号
    //-8  SIGFPE   Floating point exception
    // 如何证明？
    // 收到信号，不一定会引起进程退出 -- 没有退出，有可能还会被调度
    // CPU内部寄存器只有一份，但是寄存器中的内容，属于当前进程的上下文！
    // 你没有能力或动作修正这个问题。
    // 当进程每次被切换的时候，就有无数次状态寄存器被保存和恢复的过程
    // 所以每一次恢复的时候，就让OS识别到了CPU内部的状态寄存器中的溢出标志位是1
    // OS如何得知应该给当前进程发送8号信号的 -- OS怎么知道我除0了呢？？
    // CPU在寄存器上运算，运算结果是有问题的，就会把CPU里面的状态寄存器中的溢出标记位由0置1 --> CPU触发运算异常 --> cpu被OS管理，所以OS知道CPU异常 --> OS发信号

    /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    // 2.调用系统函数向进程发送信号
    // kill()可以向任意进程发送任意信号
    // raise() 给自己 发送 任意信号  -- kill(getpid(),任意信号)  可以自己封装 这两个都是C给我们的接口
    // abort() 给自己 发送 指定信号  -- 6号信号 kill(getpid(),SIGABORT)
    // 关于信号处理的行为的理解： 很多的情况，进程收到大部分的信号(%60)，默认处理动作都是终止进程
    // 信号的意义：信号的不同，代表不同的事件，但是对事件发送之后的处理动作可以一样！
    // int cnt = 0;
    // while (cnt <= 10)
    // {
    //     printf("cnt:%d\n", cnt++);
    //     sleep(1);
    //     if (cnt >= 5)
    //         abort(); // kill(getpid(),SIGABORT)
    //     // if(cnt >= 5) raise(9); // kill(getpid(),signo)
    // }

    /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    // if(argc != 3)
    // {
    //     Usage(argv[0]);//外面传进来的参数是常量，不能被修改？？
    //     exit(1);
    // }
    // pid_t id = atoi(argv[1]);
    // int signo = atoi(argv[2]);
    // int n = kill(id,signo);//自己实现了一个kill命令
    // if(n != 0)
    // {
    //     perror("kill");
    // }

    /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    // 这里是signal函数的调用，并不是handler的调用
    // 仅仅是设置了对2号信号的捕捉方法，并不代表该方法被调用了
    // 一般这个方法不会执行，除非收到了对应的信号！
    // signal(2,handler);//会保存到信号处理表，当进程收到信号的时候，程序会中断当前的执行流去执行信号处理函数

    // while(1)
    // {
    //     std::cout << "我是一个进程：" << getpid() << std::endl;
    //     sleep(1);
    // }
    return 0;
}







////////////////////////////////////////信号的产生/////////////////////////////////////////


////////////////////////////////////////进程信号/////////////////////////////////////////

//         信号的产生              信号的保存              信号的处理
// 共识：信号是进程发的
// 进程是如何认识信号的？ 认识 + 动作
// 进程本身是被程序员编写的属性和逻辑的集合 --- 程序员编码完成的
// 进程收到信号的时候，进程可能正在执行更重要的代码，所以信号不一定被立即处理
// 进程本身必须要有对信号的保存能力
// 进程在处理信号的时候，一般有三种动作(默认，自定义，忽略) ； 进程处理信号称之为[信号被捕捉]

// 如果一个信号是发送给进程的，而进程要保存，那么应该保存在哪里？ task_struct!
// 如何保存呢？ 是否 收到了指定的信号[1,31](普通信号) --> struct task_struct{unsigned int signal;}
// int类型有32个比特位    比特位的位置(信号编号) 比特位的内容(是否收到该信号，0没有，1表示有)
// 如何理解信号的发送呢？ 信号发送的本质：修改PCB中的信号位图！
// PCB：是内核维护的数据结构对象 -> PCB的管理者是OS，谁有权利修改PCB中的内容呢？？ OS！！
// 所以无论多少种发送信号的方式，本质都是通过OS向目标进程发送的信号！！
// 用户如果要想进程发送信号：OS必须提供发送信号处理信号的相关系统调用！！(kill命令 -- 一定调用了系统调用接口)

////////////////////////////////////////系统调用接口/////////////////////////////////////

// #include <signal.h>
// typedef void (*sighandler_t)(int);
// sighandler_t signal(int signum, sighandler_t handler);signum：处理几号信号；handler：处理信号的方法

// #include <signal.h>
// int raise(int sig);

// #include <stdlib.h>
// void abort(void);

// #include <unistd.h>
//unsigned int alarm(unsigned int seconds); 触发时钟


/////////////////////////////////////////产生信号的方法////////////////////////////////////

//                                  1.通过终端按键产生信号
// ctrl+c：2号信号
// ctrl+\: 3号信号

//                                  2.调用系统函数向进程发送信号
// #include <sys/types.h>
// #include <signal.h>
// int kill(pid_t pid, int sig);
// int raise(int sig);
// void abort(void);

//                                  3.硬件异常产生信号

//                                  4.软件条件
// 任意一个进程，都可以通过alarm系统调用在内核中设置闹钟，OS内可能会存在着很多的闹钟
// 那么OS也要管理这些闹钟。
// 用最小堆管理


///////////////////////////////////////核心转储问题////////////////////////////////////////


// 数组越界不一定导致程序崩溃！
// 你申请的空间与OS给你的空间不一样大

// Trem:异常退出
// Core:异常退出+OS会做一些事情：云服务器媒默认关闭了core file选项

// 核心转储：当进程出现异常的时候，我们将进程在对应的时刻，在内存中的有效数据转储到磁盘中 -- 核心转储！
// 为什么要有核心转储？ 支持调试(能高数我们哪里出错)
// 如何支持？ gdb上下文中 core—file core.xxx



////////////////////////////////////////信号的保存////////////////////////////////////////////////

///////////////////////////////////////信号其他相关常见概念////////////////////////////////////////


// 递达(Delivery)  --> 对信号做动作
// 信号从产生到递达之间的状态,称为信号未决(Pending)  --> 记住了，就是不做
// 进程可以选择阻塞 (Block )某个信号。  
// 被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作.
// 注意,阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作。


///////////////////////////////////////内核中，信号的基本数据结构构成////////////////////////////////////////


// task_struct{                                                               //因为阻塞了，所以不会被抵达 除非未来解除阻塞
//     unsigned int pending = 0;  // 0000 0000 0000 0000 0000 0000 0000 0000 -- 位图：a.比特位的位置，信号编号 b.比特位的内容，是否 收到了对应的信号
//     unsigned int block  =0;    // 0000 0000 0000 0000 0000 0000 0000 0000 -- 位图：a.比特位的位置，信号编号 b.比特位的内容，是否 阻塞了对应的信号
// }


// if((1 << (signo - 1)) & pcb->block)
// {
//     //signo信号是被阻塞的，不递达
// }
// else{
//     if((1 << (signo - 1)) & pcb->pending)
//     {
//         //递达该信号
//     }
// }

// typedef void(*handlder_t)(int signo);
// handler_t handler[32] = {0};// a.数组的位置(下标)，信号的编号 b.数值下标对应的内容，表示对应信号的处理方法
//我们用的signal(signo,handler);其实就是通过signo找到函数指针数值的下标，把函数指针数值下标对应的地址改成我的handler的地址

// 结论：
// 如果一个信号没有参数，并不妨碍他可以被先阻塞


///////////////////////////////////////信号的捕捉流程////////////////////////////////////////


//信号产生的时候，不会被立即处理，而是在合适的时候  --> 内核态返回用户态的时候，进行处理 --> 曾经我一定是先进入了内核态！(系统调用、进程切换)



///////////////////////////////////////内核态和用户态////////////////////////////////////////


//                  普通用户无法以用户态的身份来执行系统调用，必须让自己的状态变成内核态

// 用户为了访问内核或者硬件资源，必须通过系统调用来访问
// 实际执行系统调用"人"是"进程",但是身份其实是内核  --> 往往系统调用比较费时一点(身份改变) ，尽量避免频繁调用系统调用
// 在CPU寄存器中的CR3用比特位表示进程的运行级别： 0：内核态 3：用户态
// 进程是怎么跑到OS中执行方法呢？
// mm_struct:
// 0~3G:用户空间 -> 用户级页表(都有自己独立的用户级页表)
// 3~4G:内核空间 -> 内核级页表(只有一份就够了，不需要切换)
// 每一个进程都有自己的地址空间(用户空间独占) 内核空间(被银蛇到了每一个进程的3~4G)：进程要访问OS的接口，起始只需要在自己的地址空间上进程跳转就可以了！！
// 每个进程都有3~4GB，都会共享一个内核级页表，无论进程如何切换，都不会更改任何的[3,4]
// 用户，凭什么能执行访问内核的接口或者数据呢？(身份转换：系统调用接口，起始的位置会帮我做！)
// 我们能不能以内核态的身份，实行用户态代码?(技术上能，但是OS不相信任何人，我不知道handler方法是不是恶意代码，所以不能！！ )

// 还有些寄存器能记住页表的起始地址
// 用户态的 -> 1.操作系统自身的资源(getpid,waitpid..) 2.硬件资源(printf,write,read) ,vector(增容)